package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.vm.PagingManager;
import nachos.vm.SwapManager;

import java.io.EOFException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
    /**
     * Allocate a new process.
     */
    public UserProcess() {
    	
    	//tag the first process
    	if (!firstCreated){
    		isFirstProcess = true;
    		firstCreated = true;
    	}
    	// Refer to the stdin and stdout as 0 and 1 file descriptor
    	for (int i = 0; i < 16; i ++){
    		openedFileTable.add(null);
    	}
    	openedFileTable.set(0, UserKernel.console.openForReading());
    	openedFileTable.set(1, UserKernel.console.openForWriting());
    	
		int numPhysPages = Machine.processor().getNumPhysPages();
		pageTable = new TranslationEntry[numPhysPages];
		
		// Register in the processList and increase the processCounter
		processList.put(processCounter, this);
		processID = processCounter;
		processCounter++;			
		
    }
    
    /**
     * Allocate and return a new process of the correct class. The class name
     * is specified by the <tt>nachos.conf</tt> key
     * <tt>Kernel.processClassName</tt>.
     *
     * @return	a new process of the correct class.
     */
    public static UserProcess newUserProcess() {
    	
    	return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
    }

    /**
     * Execute the specified program with the specified arguments. Attempts to
     * load the program, and then forks a thread to run it.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the program was successfully executed.
     */
    public boolean execute(String name, String[] args) {
		if (!load(name, args)){
			System.out.println("load fail!");
		    return false;
		}
		
		thread = new UThread(this);
		thread.setName(name).fork();		
		return true;
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
    	Machine.processor().setPageTable(pageTable);
    }

    /**
     * Read a null-terminated string from this process's virtual memory. Read
     * at most <tt>maxLength + 1</tt> bytes from the specified address, search
     * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
     * without including the null terminator. If no null terminator is found,
     * returns <tt>null</tt>.
     *
     * @param	vaddr	the starting virtual address of the null-terminated
     *			string.
     * @param	maxLength	the maximum number of characters in the string,
     *				not including the null terminator.
     * @return	the string read, or <tt>null</tt> if no null terminator was
     *		found.
     */
    public String readVirtualMemoryString(int vaddr, int maxLength) {
		Lib.assertTrue(maxLength >= 0);
	
		byte[] bytes = new byte[maxLength+1];
	
		int bytesRead = readVirtualMemory(vaddr, bytes);
	
		for (int length=0; length<bytesRead; length++) {
		    if (bytes[length] == 0)
		    	return new String(bytes, 0, length);
		}
	
		return null;
    }

    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
    	return readVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
		Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);
	
		byte[] memory = Machine.processor().getMemory();
		
		// for now, just assume that virtual addresses equal physical addresses
		if (vaddr < 0 || vaddr >= memory.length)
		    return 0;

		int amount = 0, tamount;// total size read		
		int vpn, ppn, srcOffset, desOffset = offset;
		
		// Read the first page
		vpn = vaddr/pageSize;
		ppn = pageTable[vpn].ppn;
		srcOffset = vaddr%pageSize + ppn*pageSize;
		tamount = Math.min(pageSize - vaddr%pageSize, length);
		Lib.debug(dbgProcess, "read virtual memory, memory length = " + memory.length + 
				"   srcOffset = " + srcOffset + 
				"   desOffset = " + desOffset + 
				"   tamount = " + tamount);
		System.arraycopy(memory, srcOffset, data, desOffset, tamount);		
		amount += tamount;
		desOffset += tamount;
		informPageUsed(vpn);
		vpn++;
		
		// Read pages in the medium
		while(vpn < (vaddr+length)/pageSize){
			ppn = pageTable[vpn].ppn;
			srcOffset = ppn*pageSize;
			tamount = pageSize;
			Lib.debug(dbgProcess, "srcOffset = " + srcOffset + "   desOffset = " + desOffset + "   tamount = " + tamount);
			System.arraycopy(memory, srcOffset, data, desOffset, tamount);
			amount += tamount;
			desOffset += tamount;
			informPageUsed(vpn);
			vpn++;
		}
		
		// Read the last page
		if (vpn == (vaddr+length)/pageSize){
			informPageUsed(vpn);
			ppn = pageTable[vpn].ppn;
			srcOffset = ppn * pageSize;
			tamount = (vaddr+length)%pageSize;
			Lib.debug(dbgProcess, "srcOffset = " + srcOffset + "   desOffset = " + desOffset + "   tamount = " + tamount);
			System.arraycopy(memory, srcOffset, data, desOffset, tamount);
			amount += tamount;
		}
		return amount;
		
    }

    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
    	return writeVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) {
    	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);
	
		byte[] memory = Machine.processor().getMemory();
		
		// for now, just assume that virtual addresses equal physical addresses
		if (vaddr < 0 || vaddr >= memory.length)
		    return 0;
	
		/*
		int amount = Math.min(length, memory.length-vaddr);
		System.arraycopy(data, offset, memory, vaddr, amount);
		*/
		
		int amount = 0, tamount;// total size write		
		int vpn, ppn, srcOffset = offset, desOffset;
		
		// Write the first page
		vpn = vaddr/pageSize;
		if(pageTable[vpn].readOnly)// Is this section read-only?
			return 0;
		ppn = pageTable[vpn].ppn;
		desOffset = vaddr%pageSize + ppn*pageSize;
		tamount = Math.min(pageSize - vaddr%pageSize, length);
		Lib.debug(dbgProcess, "write virtual memory, srcOffset = " + srcOffset + "   desOffset = " + desOffset + "   tamount = " + tamount);
		System.arraycopy(data, srcOffset, memory, desOffset, tamount);		
		amount += tamount;
		srcOffset += tamount;
		informPageDirty(vpn);
		vpn++;
		
		// Write pages in the medium
		while(vpn < (vaddr+length)/pageSize){
			ppn = pageTable[vpn].ppn;
			desOffset = ppn*pageSize;
			tamount = pageSize;
			Lib.debug(dbgProcess, "srcOffset = " + srcOffset + "   desOffset = " + desOffset + "   tamount = " + tamount);
			System.arraycopy(data, srcOffset, memory, desOffset, tamount);
			amount += tamount;
			srcOffset += tamount;
			informPageDirty(vpn);
			vpn++;
		}
		
		// Write the last page
		if (vpn == (vaddr+length)/pageSize){
			informPageDirty(vpn);
			ppn = pageTable[vpn].ppn;
			desOffset = ppn * pageSize;
			tamount = (vaddr+length)%pageSize;
			Lib.debug(dbgProcess, "srcOffset = " + srcOffset + "   desOffset = " + desOffset + "   tamount = " + tamount);
			System.arraycopy(data, srcOffset, memory, desOffset, tamount);
			amount += tamount;
		}
		return amount;
    }
    
    protected void informPageUsed(int vpn){
    }
    
    protected void informPageDirty(int vpn){
    	
    }

    /**
     * Load the executable with the specified name into this process, and
     * prepare to pass it the specified arguments. Opens the executable, reads
     * its header information, and copies sections and arguments into this
     * process's virtual memory.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the executable was successfully loaded.
     */
    protected boolean load(String name, String[] args) {
		Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
		
		OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
		if (executable == null) {
		    Lib.debug(dbgProcess, "\topen failed");
		    return false;
		}
	
		try {
		    coff = new Coff(executable);
		}
		catch (EOFException e) {
		    executable.close();
		    Lib.debug(dbgProcess, "\tcoff load failed");
		    return false;
		}
			
		// make sure the sections are contiguous and start at page 0
		numPages = 0;
		for (int s=0; s<coff.getNumSections(); s++) {
		    CoffSection section = coff.getSection(s);
		    if (section.getFirstVPN() != numPages) {
				coff.close();
				Lib.debug(dbgProcess, "\tfragmented executable");
				return false;
		    }
		    numPages += section.getLength();
		}
	
		// make sure the argv array will fit in one page
		byte[][] argv = new byte[args.length][];
		int argsSize = 0;
		for (int i=0; i<args.length; i++) {
		    argv[i] = args[i].getBytes();
		    // 4 bytes for argv[] pointer; then string plus one for null byte
		    argsSize += 4 + argv[i].length + 1;
		}
		if (argsSize > pageSize) {
		    coff.close();
		    Lib.debug(dbgProcess, "\targuments too long");
		    return false;
		}
	
		// program counter initially points at the program entry point
		initialPC = coff.getEntryPoint();	
	
		// next comes the stack; stack pointer initially points to top of it
		numPages += stackPages;
		initialSP = numPages*pageSize;
	
		// and finally reserve 1 page for arguments
		numPages++;
	
		if (!loadSections())
		    return false;
	
		// store arguments in last page
		int entryOffset = (numPages-1)*pageSize;
		int stringOffset = entryOffset + args.length*4;
	
		this.argc = args.length;
		this.argv = entryOffset;
		
		for (int i=0; i<argv.length; i++) {
			Lib.debug(dbgProcess, "writing argument[" + i + "] = " + args[i]);
		    byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
		    Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
		    entryOffset += 4;
		    Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
			       argv[i].length);
		    stringOffset += argv[i].length;
		    Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
		    stringOffset += 1;
		}
	
		return true;
    }

    /**
     * Allocates memory for this process, and loads the COFF sections into
     * memory. If this returns successfully, the process will definitely be
     * run (this is the last step in process initialization that can fail).
     *
     * @return	<tt>true</tt> if the sections were successfully loaded.
     */
    protected boolean loadSections() {
		if (numPages > Machine.processor().getNumPhysPages()) {
		    coff.close();
		    Lib.debug(dbgProcess, "\tinsufficient physical memory");
		    return false;
		}
		
		// Get free pages
		LinkedList<Integer> freePageList = UserKernel.freePageMonitor.getFreePage(numPages);

		// load sections
		for (int s = 0; s < coff.getNumSections(); s++) {
		    CoffSection section = coff.getSection(s);
		    
		    Lib.debug(dbgProcess, "\tinitializing " + section.getName()
			      + " section (" + section.getLength() + " pages)");
	
		    for (int i = 0; i < section.getLength(); i++) {
				int vpn = section.getFirstVPN()+i;
		
				// For now, just assume virtual addresses=physical addresses
				//section.loadPage(i, vpn);
				
				// Load a page
				int phPageNum = freePageList.pop();
				section.loadPage(i, phPageNum);
				Lib.debug(dbgProcess, "load page, spn = " + i + " ppn = " + phPageNum);
				
				// We need to register this page in the pageTable of this process.
				TranslationEntry te = new TranslationEntry(
						vpn, phPageNum, true, false, false, false);
				te.ppn = phPageNum;
				te.vpn = vpn;
				te.readOnly = section.isReadOnly();
				pageTable[vpn] = te;
		    }
		}
		
		// Load the stack pages
		int vpn = initialSP / pageSize -1;
		for (int i = 0; i < stackPages; i ++){
			int phPageNum = freePageList.pop();
			
			// We need to register this page in the pageTable of this process.
			TranslationEntry te = new TranslationEntry(
					vpn, phPageNum, true, false, false, false);
			te.ppn = phPageNum;
			te.vpn = vpn;
			te.readOnly = false;
			pageTable[vpn] = te;
			Lib.debug(dbgProcess, "load stack page, vpn = " + te.vpn + " ppn = " + te.ppn);
			
			vpn--;
		}
		
		// Load the last page for the arguments
		vpn = numPages - 1;
		int phPageNum = freePageList.pop();
		
		// We need to register this page in the pageTable of this process.
		TranslationEntry te = new TranslationEntry(
			vpn, phPageNum, true, false, false, false);
		te.ppn = phPageNum;
		te.vpn = vpn;
		pageTable[vpn] = te;
		Lib.debug(dbgProcess, "load page for argv, vpn = " + te.vpn + " ppn = " + te.ppn);
		
		
		return true;
    }

    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {
    	// Release pages to the freePageMonitor
    	for (int i = 0; i < numPages; i ++){
    		Lib.debug(dbgProcess, "unload page, vpn = " + pageTable[i].vpn + "   ppn = " + pageTable[i].ppn);
    		UserKernel.freePageMonitor.returnFreePage(pageTable[i].ppn);
    	}
    	coff.close();
    	System.out.println("close " + this.processID + "'s executable");
    }    

    /**
     * Initialize the processor's registers in preparation for running the
     * program loaded into this process. Set the PC register to point at the
     * start function, set the stack pointer register to point at the top of
     * the stack, set the A0 and A1 registers to argc and argv, respectively,
     * and initialize all other registers to 0.
     */
    public void initRegisters() {
	Processor processor = Machine.processor();

	// by default, everything's 0
	for (int i=0; i<Processor.numUserRegisters; i++)
	    processor.writeRegister(i, 0);

	// initialize PC and SP according
	processor.writeRegister(Processor.regPC, initialPC);
	processor.writeRegister(Processor.regSP, initialSP);

	// initialize the first two argument registers to argc and argv
	processor.writeRegister(Processor.regA0, argc);
	processor.writeRegister(Processor.regA1, argv);
    }

    /**
     * Handle the halt() system call. 
     */
    private int handleHalt() {
    	
    	// this system call can only be execute by the root process
    	if (isFirstProcess){
    		// Release the resources 
    		unloadSections();
    		
    		SwapManager.releaseAll();
    		PagingManager.printPageFaultCount();
    		Machine.halt();
    		Lib.assertNotReached("Machine.halt() did not halt machine!");
    	}
    	else{
    		Lib.debug(dbgProcess, "Halt can only call by RootProcess!");
    	}
		return 0;
    }
    
    /**
     * Handle the create(char *name) system call, 
     * attend to open a file, create if it does not  exist. 
     * 
     * @param	the virtual address of the filename
     * 
     * @return	file descriptor or -1 for err 
     */
    private int handleCreate(int vaddr) {
    	// Read filename
    	String fileName = readVirtualMemoryString(vaddr, maxLen);

    	// If already exists in the openedFileTable, just return -1
    	if (openedFileTable.contains(fileName)){
    		Lib.debug(dbgProcess, fileName + " already opened!");
    		return -1;
    	}
    	
    	// The filename cannot be longer than 256 bytes
    	if (fileName.length() > maxLen){
    		Lib.debug(dbgProcess, "Filename too long!");
    		return -1;
    	}
    	
    	// If this file happened to be in the deleting list, cannot open or create!
    	if (openedFileCounter.containsKey(fileName)){
    		Lib.debug(dbgProcess, "same file " + fileName + " cannot be created twice!");
    		return -1;
    	}
    	
    	// Register in the openedFileTable, find a position first
    	int fd;
    	for (fd = 0; fd < openedFileTable.size(); fd ++){
    		if (openedFileTable.get(fd) == null)
    			break;
    	}
    	// If openedFiletable is full, cannot continue.
    	if (fd >= 16){
    		Lib.debug(dbgProcess, "Cannot create file" + fileName + " : file descriptor buffer full!");
    		return -1;
    	}
    	
    	// Open the file, create while not exists
    	OpenFile of = UserKernel.fileSystem.open(fileName, false);
    	
    	if (of == null){
    		of = UserKernel.fileSystem.open(fileName, true);
    		Lib.debug(dbgProcess, fileName + " does not exist, create a new file!");
    		if (of == null){
    			Lib.debug(dbgProcess, "file system err while create!");
    			return -1;// if cannot execute this open action, return -1
    		}
    	}    	
    	
    	openedFileTable.set(fd, of);
    	//filePointer.put(of, 0);
    	
    	// Register in the openedFileCounter
    	if (openedFileCounter.containsKey(of)){
    		openedFileCounter.put(of.getName(), openedFileCounter.get(of)+1);
    	}else{
    		openedFileCounter.put(of.getName(), 1);
    	}
    	Lib.debug(dbgProcess, "create    fd = " + fd + " : " + fileName);
    	return fd;
    	
    }
    
    /**
     * Handle the open(char *name) system call
     * 
     * @return 
     */
    private int handleOpen(int vaddr){
    	// get the filename
    	String fileName = this.readVirtualMemoryString(vaddr, maxLen);
    	
    	// If already exists in the openedFileTable, just return -1
    	if (openedFileTable.contains(fileName)){
    		Lib.debug(dbgProcess, fileName + " already opened!");
    		return -1;
    	}
    	
    	// If this file happened to be in the deleting list, cannot open or create!
    	if (openedFileCounter.containsKey(fileName)){
    		Lib.debug(dbgProcess, "same file " + fileName + " cannot be created twice!");
    		return -1;
    	}
    	
    	OpenFile of = UserKernel.fileSystem.open(fileName, false);
    	if (of == null){
    		Lib.debug(dbgProcess, "cannot open " + fileName);
    		return -1;// return -1 while an err occur
    	}
    	else{
    	// Register in the openedFileTable, find a position first
    		int fd;
    		for (fd = 0; fd < openedFileTable.size(); fd ++){
    			if (openedFileTable.get(fd) == null)
    				break;
    		}
    		// If openedFiletable is full, cannot continue.
        	if (fd >= 16){
        		Lib.debug(dbgProcess, "Cannot open file" + fileName + " : file descriptor buffer full!");
        		return -1;
        	}
    		openedFileTable.set(fd, of);
    		//filePointer.put(of, 0);
    		
    		//  Register in the openedFileCounter
    		if (openedFileCounter.containsKey(of)){
    			openedFileCounter.put(of.getName(), openedFileCounter.get(of) + 1);
    		}
    		else{
    			openedFileCounter.put(of.getName(), 1);
    		}   		
    		
    		Lib.debug(dbgProcess, "open      fd = " + fd + " : " + fileName);
    		return fd;
    	}
    	
    }

    /**
     * Handle the read(int fd, char *buffer, int size) system call
     * 
     * @param	fileDescriptor	source file descriptor to read
     * @param	bufAddr	target buffer to put the data in
     * @param	size	number of bytes to read
     * 
     * @return the number of bytes read if success, or -1 if error occurred
     */
    private int handleRead(int fileDescriptor, int bufAddr, int size){
    	// According to the fileDescriptor we can get the OpenFile object.
    	OpenFile of;
    	if (fileDescriptor >= 0 && fileDescriptor < openedFileTable.size() && openedFileTable.get(fileDescriptor) != null)
    		of = openedFileTable.get(fileDescriptor);
    	else{
    		Lib.debug(dbgProcess, "error in read : invalid file descriptor " + fileDescriptor);
    		return -1;
    	}
    	
    	// Get the content of the file into the buffer.
    	byte[] buf = new byte[size];
    	int readSize = of.read(buf, 0, size);
    	if (readSize == -1){
    		Lib.debug(dbgProcess, "error while reading!");
    		return -1;// if any error occurred, return -1
    	}
    	
    	// Write into the virtual memory.
    	int result;
    	result = writeVirtualMemory(bufAddr, buf, 0, readSize);
    	
    	Lib.debug(dbgProcess, "read      fd = " + fileDescriptor + ", " + result + " bytes");
    	return result;
    	
    }

    /**
     * Handle the write(int fileDescriptor, void *buffer, int count) system call
     * 
     * @return  the number of bytes written on success, or -1 if an error occurred.
     */
    private int handleWrite(int fileDescriptor, int bufAddr, int count){
    	// According to the fileDescriptor we can get the OpenFile object.
    	OpenFile of;
    	if (fileDescriptor >= 0 && fileDescriptor < openedFileTable.size() && openedFileTable.get(fileDescriptor) != null)
    		of = openedFileTable.get(fileDescriptor);
    	else{
    		Lib.debug(dbgProcess, "error in write : invalid file descriptor " + fileDescriptor);
    		return -1;
    	}
    	
    	// Read in the content from the virtual memory
    	byte[] data = new byte[count];
    	int readSize;
    	readSize = this.readVirtualMemory(bufAddr, data, 0, count);
    		
    	//write it into the file system
    	int result = of.write(data, 0, readSize);
    	if (result == -1)
    		Lib.debug(dbgProcess, "error while write!");
    	else
    		Lib.debug(dbgProcess, "write     fd = " + fileDescriptor + ", " + readSize + " bytes");
    	return result;
    	
    }
    
    /**
     * Handle the close(int fileDescriptor) system call
     * 
     * @return 0 on success, or -1 if an error occurred.
     */
    private int handleClose(int fileDescriptor){
    	// First, we need to get the OpenFile object according to the file descriptor
    	OpenFile of;
    	if (fileDescriptor >= 0 && fileDescriptor < openedFileTable.size() && openedFileTable.get(fileDescriptor) != null)
    		of = openedFileTable.get(fileDescriptor);
    	else{
    		Lib.debug(dbgProcess, "error in close : invalid file descriptor " + fileDescriptor);
    		return -1;
    	}    	
    	
    	int result = closeFile(of);
    	Lib.debug(dbgProcess, "close     fd = " + fileDescriptor);
    	return result;
    }
    
    
    private int closeFile(OpenFile of){    	
    	
    	// remove its info from the openedFileTable and the openedFileCounter
    	openedFileTable.set(openedFileTable.indexOf(of), null);
    	//filePointer.remove(of);
    	int count = openedFileCounter.get(of.getName());
    	
    	// If no one else is opening this file.
    	if (count == 1){
    		openedFileCounter.remove(of.getName());
    		// Check whether this file need to be  deleted, if is, delete it!
    		if (deleteFileList.contains(of.getName())){
    			UserKernel.fileSystem.remove(of.getName());
    			deleteFileList.remove(of.getName());
    			Lib.debug(dbgProcess, "close and remove file : " + of.getName());
    		}
    	}
    	else
    		openedFileCounter.put(of.getName(), count-1);
 
    	
    	// close the file
    	of.close();
    	Lib.debug(dbgProcess, "close " + of.getName());
    	
    	return 0;
    }
    
    /**
     * Handle the unlink(char *name) system call
     * @param offset of the filename in the virtual address.
     * @return 0 on success, or -1 if an error occurred.
     */
    private int handleUnlink(int vaddr){
    	// Get the filename first
    	String filename = this.readVirtualMemoryString(vaddr, maxLen);
    	
    	// Check the openedFileCounter, is there anyone's using this file ?
    	if(openedFileCounter.containsKey(filename)){
    		// Oops, cannot delete it right now, save it to the deleteFileList and delete it later. 
    		deleteFileList.add(filename);
    		Lib.debug(dbgProcess, "unlinking " + filename + ", remove later");
    		return 0;
    	}
    	else{
        	// Delete the file now.
        	boolean rmResult = UserKernel.fileSystem.remove(filename);
        	if (rmResult){
        		Lib.debug(dbgProcess, "remove    " + filename);
        		return 0;
        	}
        	else{
        		Lib.debug(dbgProcess, "error while remove " + filename);
        		return -1;
        	}
    	}
    }
    
    /**
     * Handle the <tt>exit(int status)</tt> system call
     * 
     * @param	status
     * 
     * @return	<tt>exit()</tt> never returns!
     */
    private int handleExit(int status){
    	// Close the opened file descriptors
    	for (int i = 2; i < openedFileTable.size(); i ++){
    		OpenFile of = openedFileTable.get(i);
    		if (of != null){
    			this.closeFile(of);
    		}
    	}    	
    	
    	// Release the resources
    	this.unloadSections();
    	
    	// Remove from the global process list
    	processList.remove(this.processID);
    	
    	// If it's the last process, terminate the machine
    	if (processList.isEmpty()){
    		Lib.debug(dbgProcess, "last process exit, call Kernel.terminate");
    		Kernel.kernel.terminate();
    	}
    	
    	// If it has a parent process, return the status
    	if (parentProcess != null){
    		parentProcess.childReturn.put(this.processID, status);
    		Lib.debug(dbgProcess, "put status " + status + " for " + this);
    	}
    	
    	// Finish this process!
    	UThread.finish();
    	
    	Lib.assertNotReached("Exit never returns!");
    	return -1;
    }
    
    /**
     * Handle join(int processID, int *status) system call
     * 
     * @param	processID	process ID of the child process to join
     * @param	statusAddr	address of status to return 
     * 
     * @return If the child exited normally, returns 1. 
     * If the child exited as a result of an unhandled exception, returns 0. 
     * If processID does not refer to a child process of the current process, returns -1.
     */
    private int handleJoin(int processID, int statusAddr){
    	// First, we get the child process
    	UserProcess childProcess;
    	if (processList.containsKey(processID)){
    		childProcess = processList.get(processID);
    	}
    	else {
    		return -1;
    	}
    	    	
    	// And now, we wait....
    	childProcess.thread.join();
    	
    	// Pick the return value of the child process, write it to the virtual file system  
    	if (childReturn.containsKey(childProcess.processID)){
    		int statusReturn = childReturn.get(childProcess.processID);
    		Lib.debug(dbgProcess, "join : get child status = " + statusReturn);
    		this.writeVirtualMemory(statusAddr, Lib.bytesFromInt(statusReturn));
    	}
    	
    	return 1;
    }
    
    /**
     * Handle the exec(char *file, int argc, char *argv[]) system call
     * 
     * @return	child process id, or -1 on error 
     */
    private int handleExec(int fileNameAddr, int argCount, int argAddr){
    	
    	// Get the filename
    	String filename = this.readVirtualMemoryString(fileNameAddr, maxLen);
    	if (filename == null){
    		System.out.println("exec wrong :  + filename = null ");
    		return -1;
    	}
    	
    	// Check file exists
    	OpenFile ftry = UserKernel.fileSystem.open(filename, false);
    	if (ftry == null){
    		System.out.println("exec wrong : " + filename + " open fail!");
    		return -1;
    	}
    	ftry.close();
    	
    	// Create a new Process
    	UserProcess up = new UserProcess();
    	up.parentProcess = this;
    	    	
    	// Get the arguments
    	String []arguments = new String[argCount];
    	byte[] pointer = new byte[4];
    	for (int i = 0; i < argCount; i ++){
    		// read in a pointer
    		this.readVirtualMemory(argAddr, pointer);
    		int intPointer = Lib.bytesToInt(pointer, 0);
    		Lib.debug(dbgProcess, "argAddr = " + argAddr + "   intPointer = " + intPointer);
    		argAddr += 4;
    		// read real content
    		String argument = this.readVirtualMemoryString(intPointer, maxLen);
    		if (argument != null){
    			arguments[i] = argument;
    			Lib.debug(dbgProcess, "\targ[" + i + "] = " + arguments[i] + 
        				" length = " + arguments[i].length());
    		}
    		
    	}
    	
    	// Execute it !
    	if (up.execute(filename, arguments)){
    		// Return the ID of the new process
    		Lib.debug(dbgProcess, "process id = " + up.processID);
    		return up.processID;
    	}
    	else{
    		//up.handleExit(0);
    		return -1;
    	}
    }
    
    /**
     * 
     * @param fd the target file's descriptor
     * @param offset the offset to set
     * @param whence SEEK_SET : the absolute position
     * SEEK_CUR the relative position
     * SEEK_END the appending position
     * @return the absolute position if success, or -1 on error
     */
    private int handleLseek(int fd, int  offset, int whence){
    	// First, we get the OpenFile.
    	OpenFile of = openedFileTable.get(fd);
    	if (of == null){
    		Lib.debug(dbgProcess, "lseek wrong ! of = null");
    		return -1;
    	}
    	
    	// Then, we calculate the new position.
    	int newOffset;
    	switch (whence){
    	case SEEK_SET:
    		if (offset < 0){
    			Lib.debug(dbgProcess, "lseek wrong ! seek_set offset < 0");
    			return -1;
    		}
    		newOffset = offset;
    		break;
    	case SEEK_END:
    		newOffset = of.length() + offset;
    		if (newOffset < 0){
    			Lib.debug(dbgProcess, "lseek wrong ! seek_end offset < 0");
    			return -1;
    		}    			
    		break;
    	case SEEK_CUR:
    		newOffset = of.tell() + offset;
    		if (newOffset < 0){
    			Lib.debug(dbgProcess, "lseek wrong ! seek_cur offset < 0");
    			return -1;
    		}
    		break;
    	default:{
    		Lib.debug(dbgProcess, "lseek wrong ! invalid whence mode");
    		return -1;
    		}
    	}    
    	
    	// Set the new position finally.
    	of.seek(newOffset);
    	Lib.debug(dbgProcess, "lseek     : set filepointer : " + of + " -> " + newOffset);
    	return newOffset;
    }
    
    /**
     * Handle the sleep(int time) system call
     * 
     * @param time the number of the ticks this process want to sleep
     * @return normally 0, -1 on error
     */
    private int handleSleep(int time){
    	Lib.debug(dbgProcess, "syscall sleep for " + time + " ticks... process ID = " + this.processID);
    	UserKernel.alarm.waitUntil(time);
    	Lib.debug(dbgProcess, "wake up from sleep, process ID = " + this.processID);
    	return 0;
    }
    
    private static final int
    syscallHalt = 0,
	syscallExit = 1,
	syscallExec = 2,
	syscallJoin = 3,
	syscallCreate = 4,
	syscallOpen = 5,
	syscallRead = 6,
	syscallWrite = 7,
	syscallClose = 8,
	syscallUnlink = 9,
    syscallLseek = 13,
    syscallSleep = 14;

    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
     * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
     * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td></tr>
     * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
     * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
     * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
     * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
     * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
		switch (syscall) {
			case syscallHalt:
				return handleHalt();
				
			case syscallCreate:			
				return handleCreate(a0);		
				
			case syscallOpen:
				return handleOpen(a0);
				
			case syscallRead:
				return handleRead(a0, a1, a2);
				
			case syscallWrite:
				return handleWrite(a0, a1, a2);
				
			case syscallClose:
				return handleClose(a0);

			case syscallUnlink:
				return handleUnlink(a0);

			case syscallExit:
				return handleExit(a0);

			case syscallExec:
				return handleExec(a0, a1, a2);

			case syscallJoin:
				return handleJoin(a0, a1);

			case syscallLseek:
				return handleLseek(a0, a1, a2);
				
			case syscallSleep:
				return handleSleep(a0);
				
			default:
			    Lib.debug(dbgProcess, "Unknown syscall " + syscall);
			    Lib.assertNotReached("Unknown system call!");
		}
		return 0;
    }

    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
		Processor processor = Machine.processor();
	
		switch (cause) {
		case Processor.exceptionSyscall:
		    int result = handleSyscall(processor.readRegister(Processor.regV0),
					       processor.readRegister(Processor.regA0),
					       processor.readRegister(Processor.regA1),
					       processor.readRegister(Processor.regA2),
					       processor.readRegister(Processor.regA3)
					       );
		    processor.writeRegister(Processor.regV0, result);
		    processor.advancePC();
		    break;				       
					       
		default:
		    Lib.debug(dbgProcess, "Unexpected exception: " +
			      Processor.exceptionNames[cause]);
		    Lib.assertNotReached("Unexpected exception " + Processor.exceptionNames[cause]);
		}
    }

    /** The program being run by this process. */
    protected Coff coff;

    /** This process's page table. */
    protected TranslationEntry[] pageTable;
    /** The number of contiguous pages occupied by the program. */
    protected int numPages; 

    /** The number of pages in the program's stack. */
    public final int stackPages = 8;
    
	protected int initialPC, initialSP;
    private int argc, argv;
	
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    
    // Following modified by Sylvia, 2012.3-2012.4
    private static boolean firstCreated = false;
	public boolean isFirstProcess = false;
    
	/** The opened file's descriptor of this process */
	private ArrayList<OpenFile> openedFileTable = new ArrayList<OpenFile>();
	
	/** The each file's pointer */
	//private HashMap<OpenFile, Integer> filePointer = new HashMap<OpenFile, Integer>();
	
	/** The whole system's opened files counter */
	private static HashMap<String, Integer> openedFileCounter = new HashMap<String, Integer>();
    
	/** The max length of the filename */
	protected static int maxLen = 256;
	
	/** List of the files which need to be deleted after been closed */
    private static ArrayList<String> deleteFileList = new ArrayList<String>();
    
    /** Global counter and list of process id*/
    private static HashMap<Integer, UserProcess> processList = new HashMap<Integer, UserProcess>();
    private static int processCounter = 0;
    
    /** process ID*/
    protected int processID;
    
    /** the thread this process's on */
    protected UThread thread;
       
    /** this map store the return value of the child process on exit normally */
    private HashMap<Integer, Integer> childReturn = new HashMap<Integer, Integer>();
    
    protected UserProcess parentProcess = null;
    
    final static int SEEK_SET = 1, SEEK_CUR = 2, SEEK_END = 3; 
    
}
